/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: cbus_worker.c
 *
 * Purpose: implementation thread worker for cbus.
 *
 * Developer:
 *   wen.gu , 2022-01-18
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#include "cbus/core/cbus_worker.h"

#include "cbus/core/cbus_thread.h"

#include "cbus/core/cbus_log.h"
/******************************************************************************
 **    MACROS
 ******************************************************************************/

/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/
typedef enum _cbus_state
{
    AVP_THD_STATE_STOP = 0,
    AVP_THD_STATE_START,
    AVP_THD_STATE_PAUSE,
}cbus_state_t;

struct _cbus_worker
{
    GBOL joinable;
    cbus_state_t state;
    cbus_mutex_t lock;
    cbus_condition_variable_t cond;
    cbus_thread_t thd;
    GPTR opaque;
    cbus_worker_func worker;
    cbus_worker_loop_func looper;
};

/******************************************************************************
 **    inner FUNCTION DEFINITIONS
 ******************************************************************************/

static inline void cbus_inner_thd_func(GPTR opaque)
{
    cbus_worker_t thd_worker = (cbus_worker_t)opaque;
    
    thd_worker->worker(thd_worker, thd_worker->opaque); /** todo refine me */

    if (thd_worker->joinable == GFALSE) /** indicat detach mode, release resource needed by myself */
    {
        cbus_mutex_destroy(thd_worker->lock);
        cbus_cv_destroy(thd_worker->cond);
        free(thd_worker);           
    }

}


static inline void cbus_inner_worker(cbus_worker_t thd_worker, GPTR opaque)
{
    while (cbus_worker_running(thd_worker))
    {
        GS64 ret = thd_worker->looper(thd_worker->opaque);

        if (ret == 0)
        {
            continue; /** run loop again immediately*/
        }
        else if (ret > 0)
        {
            cbus_mutex_lock(thd_worker->lock);
            cbus_cv_wait_for(thd_worker->cond, thd_worker->lock, (GU32)ret);
            cbus_mutex_unlock(thd_worker->lock);            
        }
        else
        {
            cbus_mutex_lock(thd_worker->lock);
            thd_worker->state = AVP_THD_STATE_STOP;
            cbus_mutex_unlock(thd_worker->lock);  
            break;
        }
    }
}
/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/

GErrc cbus_worker_create(cbus_worker_t* thd_worker, const GSTR worker_name, cbus_worker_func worker, GPTR opaque)
{
    if (!thd_worker || !worker)
    {
        return G_ErrBadParameter;
    }

    cbus_worker_t tw = (cbus_worker_t)malloc(sizeof(struct _cbus_worker));

    if (!tw)
    {
        LOGE("alloc worker instance failed\n");
        return G_ErrInsufficientResources;
    }

    tw->lock = cbus_mutex_create();

    if (!tw->lock)
    {
        free(tw);
        return G_ErrInsufficientResources;
    }

    tw->cond = cbus_cv_create();

    if (!tw->cond)
    {
        cbus_mutex_destroy(tw->lock);
        free(tw);
        return G_ErrInsufficientResources;
    }

    tw->thd = cbus_thread_create(cbus_inner_thd_func, (GPTR)tw);

    if (!tw->thd)
    {
        LOGE("create thread for woker failed\n");
        cbus_mutex_destroy(tw->lock);
        cbus_cv_destroy(tw->cond);
        free(tw);
        return G_ErrInsufficientResources;
    }

    if (worker_name)
    {
        cbus_thread_set_name(tw->thd, worker_name);
    }    

    tw->state = AVP_THD_STATE_PAUSE;
    tw->opaque = opaque;
    tw->worker = worker;
    tw->joinable = GTRUE;
    *thd_worker = tw;

    return G_OK;
}

GErrc cbus_worker_create2(cbus_worker_t* thd_worker, const GSTR worker_name,  cbus_worker_loop_func looper, GPTR opaque)
{
    cbus_worker_t tw;
    GErrc ret = cbus_worker_create(&tw, worker_name, cbus_inner_worker, opaque);

    if (G_OK == ret)
    {
        tw->looper = looper;
    }

    return ret;
}

/** will be join(wait the terminate) and destroy the worker */
GErrc cbus_worker_join(cbus_worker_t thd_worker)
{
    if (!thd_worker)
    {
        return G_ErrBadParameter;
    }

    if (thd_worker->joinable == GTRUE)
    {
        cbus_mutex_lock(thd_worker->lock);
        thd_worker->state = AVP_THD_STATE_STOP;
        cbus_cv_notify_all(thd_worker->cond);
        cbus_mutex_unlock(thd_worker->lock);

        cbus_thread_join(thd_worker->thd);
        cbus_mutex_destroy(thd_worker->lock);
        cbus_cv_destroy(thd_worker->cond);
        free(thd_worker);    
        return G_OK;    
    }

    return G_ErrInvalidStatus;    
}

GErrc cbus_worker_detach(cbus_worker_t thd_worker)
{
    if (!thd_worker)
    {
        return G_ErrBadParameter;
    }

    if (thd_worker->joinable == GTRUE)
    {
        GErrc ret = cbus_thread_detach(thd_worker->thd);

        if (G_OK == ret)
        {
            thd_worker->joinable = GFALSE;
        }

        return ret;
    }

    return G_ErrInvalidStatus;
}


GBOL cbus_worker_running(cbus_worker_t thd_worker)
{
    if (thd_worker)
    {        
        cbus_mutex_lock(thd_worker->lock);
        if (thd_worker->state == AVP_THD_STATE_START)
        {
            cbus_mutex_unlock(thd_worker->lock);
            return GTRUE;
        }
        else if (thd_worker->state == AVP_THD_STATE_PAUSE)
        {
            while (thd_worker->state == AVP_THD_STATE_PAUSE)
            {
                cbus_cv_wait(thd_worker->cond, thd_worker->lock);
            }
            GBOL ret = (thd_worker->state == AVP_THD_STATE_STOP) ? GFALSE : GTRUE;
            cbus_mutex_unlock(thd_worker->lock);
            return ret;
        }
        else
        {
            cbus_mutex_unlock(thd_worker->lock);
            return GFALSE;            
        }
    }

    return GFALSE;
}

GErrc cbus_worker_start(cbus_worker_t thd_worker)
{
    if (thd_worker)
    {
        if (!thd_worker->thd)
        {
            return G_ErrInvalidStatus;
        }

        cbus_thread_run(thd_worker->thd);

        cbus_mutex_lock(thd_worker->lock);
        if (thd_worker->state == AVP_THD_STATE_PAUSE)
        {
            thd_worker->state = AVP_THD_STATE_START;
            cbus_cv_notify_all(thd_worker->cond);            
        }
        cbus_mutex_unlock(thd_worker->lock);
        return G_OK;
    }

    return G_ErrBadParameter;
}

 GErrc cbus_worker_pause(cbus_worker_t thd_worker)
{
    if (thd_worker)
    {
        cbus_mutex_lock(thd_worker->lock);
        if (thd_worker->state == AVP_THD_STATE_START) /** if already do join to stop, then do nothing */
        {
            thd_worker->state = AVP_THD_STATE_PAUSE;
        }        
        cbus_mutex_unlock(thd_worker->lock);
        return G_OK;
    }

    return G_ErrBadParameter;
}

